﻿using System;
using ch10.binary;
using ch10.binary.module;
using ch10.binary.type;
using ch10.instance;

namespace ch10.interpreter
{
    using WasmVal = Object;

    public delegate (WasmVal[], Exception) SharpFunc(WasmVal[] args);

    public struct VmFunc : IFunction
    {
        public FuncType _type;
        public Code Code;
        public IFunction Func;
        public Vm Vm;

        public static VmFunc NewExternalFunc(FuncType ft, IFunction func)
        {
            return new()
            {
                _type = ft,
                Func = func
            };
        }

        public FuncType Type()
        {
            return _type;
        }

        public (WasmVal[], Exception) Call(params WasmVal[] args)
        {
            if (Func != null)
            {
                return Func.Call(args);
            }

            return safeCall(args);
        }

        public (WasmVal[], Exception) safeCall(WasmVal[] args)
        {
            try
            {
                var results = call(args);
                return (results, null);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        public object[] call(object[] args)
        {
            pushArgs(_type, args);
            Instr.CallFunc(Vm, this);
            if (Func == null)
            {
                Vm.Loop();
            }

            return popResults(_type);
        }

        public void pushArgs(FuncType ft, object[] args)
        {
            if ((ft.ParamTypes?.Length ?? 0) != (args?.Length ?? 0))
            {
                Panic.Run($"param count: {ft.ParamTypes?.Length ?? 0:D}, arg count: {args?.Length ?? 0:D}");
            }

            for (var i = 0; i < ft.ParamTypes?.Length; i++)
            {
                var vt = ft.ParamTypes[i];
                Vm.PushU64(Instr.UnwrapU64(vt, args?[i]));
            }
        }

        public object[] popResults(FuncType ft)

        {
            var results = new object[ft.ResultTypes.Length];
            for (var n = (ft.ResultTypes.Length) - 1; n >= 0; n--)
            {
                results[n] = Instr.WrapU64(ft.ResultTypes[n], Vm.PopU64());
            }

            return results;
        }

        public static VmFunc NewInternalFunc(ref Vm vm, FuncType ft, Code code)
        {
            return new()
            {
                Vm = vm,
                _type = ft,
                Code = code,
            };
        }
    }
}